// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/message_center/views/message_view.h"

#include "ui/accessibility/ax_view_state.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/simple_menu_model.h"
#include "ui/base/ui_base_switches_util.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/shadow_value.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/message_center_style.h"
#include "ui/message_center/views/padded_button.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/painter.h"
#include "ui/views/shadow_border.h"

namespace {

const int kCloseIconTopPadding = 5;
const int kCloseIconRightPadding = 5;

const int kShadowOffset = 1;
const int kShadowBlur = 4;

} // namespace

namespace message_center {

MessageView::MessageView(MessageViewController* controller,
    const std::string& notification_id,
    const NotifierId& notifier_id,
    const gfx::ImageSkia& small_image,
    const base::string16& display_source)
    : controller_(controller)
    , notification_id_(notification_id)
    , notifier_id_(notifier_id)
    , background_view_(NULL)
    , scroller_(NULL)
    , display_source_(display_source)
{
    SetFocusable(true);

    // Create the opaque background that's above the view's shadow.
    background_view_ = new views::View();
    background_view_->set_background(
        views::Background::CreateSolidBackground(kNotificationBackgroundColor));
    AddChildView(background_view_);

    views::ImageView* small_image_view = new views::ImageView();
    small_image_view->SetImage(small_image);
    small_image_view->SetImageSize(gfx::Size(kSmallImageSize, kSmallImageSize));
    // The small image view should be added to view hierarchy by the derived
    // class. This ensures that it is on top of other views.
    small_image_view->set_owned_by_client();
    small_image_view_.reset(small_image_view);

    PaddedButton* close = new PaddedButton(this);
    close->SetPadding(-kCloseIconRightPadding, kCloseIconTopPadding);
    close->SetNormalImage(IDR_NOTIFICATION_CLOSE);
    close->SetHoveredImage(IDR_NOTIFICATION_CLOSE_HOVER);
    close->SetPressedImage(IDR_NOTIFICATION_CLOSE_PRESSED);
    close->set_animate_on_state_change(false);
    close->SetAccessibleName(l10n_util::GetStringUTF16(
        IDS_MESSAGE_CENTER_CLOSE_NOTIFICATION_BUTTON_ACCESSIBLE_NAME));
    // The close button should be added to view hierarchy by the derived class.
    // This ensures that it is on top of other views.
    close->set_owned_by_client();
    close_button_.reset(close);

    focus_painter_ = views::Painter::CreateSolidFocusPainter(
        kFocusBorderColor, gfx::Insets(0, 1, 3, 2));
}

MessageView::~MessageView()
{
}

void MessageView::UpdateWithNotification(const Notification& notification)
{
    small_image_view_->SetImage(notification.small_image().AsImageSkia());
    display_source_ = notification.display_source();
}

// static
gfx::Insets MessageView::GetShadowInsets()
{
    return gfx::Insets(kShadowBlur / 2 - kShadowOffset,
        kShadowBlur / 2,
        kShadowBlur / 2 + kShadowOffset,
        kShadowBlur / 2);
}

void MessageView::CreateShadowBorder()
{
    SetBorder(scoped_ptr<views::Border>(new views::ShadowBorder(
        gfx::ShadowValue(gfx::Vector2d(0, kShadowOffset), kShadowBlur,
            message_center::kShadowColor))));
}

bool MessageView::IsCloseButtonFocused()
{
    views::FocusManager* focus_manager = GetFocusManager();
    return focus_manager && focus_manager->GetFocusedView() == close_button();
}

void MessageView::RequestFocusOnCloseButton()
{
    close_button_->RequestFocus();
}

void MessageView::GetAccessibleState(ui::AXViewState* state)
{
    state->role = ui::AX_ROLE_BUTTON;
    state->name = accessible_name_;
}

bool MessageView::OnMousePressed(const ui::MouseEvent& event)
{
    if (!event.IsOnlyLeftMouseButton())
        return false;

    controller_->ClickOnNotification(notification_id_);
    return true;
}

bool MessageView::OnKeyPressed(const ui::KeyEvent& event)
{
    if (event.flags() != ui::EF_NONE)
        return false;

    if (event.key_code() == ui::VKEY_RETURN) {
        controller_->ClickOnNotification(notification_id_);
        return true;
    } else if ((event.key_code() == ui::VKEY_DELETE || event.key_code() == ui::VKEY_BACK)) {
        controller_->RemoveNotification(notification_id_, true); // By user.
        return true;
    }

    return false;
}

bool MessageView::OnKeyReleased(const ui::KeyEvent& event)
{
    // Space key handling is triggerred at key-release timing. See
    // ui/views/controls/buttons/custom_button.cc for why.
    if (event.flags() != ui::EF_NONE || event.flags() != ui::VKEY_SPACE)
        return false;

    controller_->ClickOnNotification(notification_id_);
    return true;
}

void MessageView::OnPaint(gfx::Canvas* canvas)
{
    DCHECK_EQ(this, close_button_->parent());
    DCHECK_EQ(this, small_image_view_->parent());
    SlideOutView::OnPaint(canvas);
    views::Painter::PaintFocusPainter(this, canvas, focus_painter_.get());
}

void MessageView::OnFocus()
{
    SlideOutView::OnFocus();
    // We paint a focus indicator.
    SchedulePaint();
}

void MessageView::OnBlur()
{
    SlideOutView::OnBlur();
    // We paint a focus indicator.
    SchedulePaint();
}

void MessageView::Layout()
{
    gfx::Rect content_bounds = GetContentsBounds();

    // Background.
    background_view_->SetBoundsRect(content_bounds);

    // Close button.
    gfx::Size close_size(close_button_->GetPreferredSize());
    gfx::Rect close_rect(content_bounds.right() - close_size.width(),
        content_bounds.y(),
        close_size.width(),
        close_size.height());
    close_button_->SetBoundsRect(close_rect);

    gfx::Size small_image_size(small_image_view_->GetPreferredSize());
    gfx::Rect small_image_rect(small_image_size);
    small_image_rect.set_origin(gfx::Point(
        content_bounds.right() - small_image_size.width() - kSmallImagePadding,
        content_bounds.bottom() - small_image_size.height() - kSmallImagePadding));
    small_image_view_->SetBoundsRect(small_image_rect);
}

void MessageView::OnGestureEvent(ui::GestureEvent* event)
{
    switch (event->type()) {
    case ui::ET_GESTURE_TAP_DOWN: {
        SetDrawBackgroundAsActive(true);
        break;
    }
    case ui::ET_GESTURE_TAP_CANCEL:
    case ui::ET_GESTURE_END: {
        SetDrawBackgroundAsActive(false);
        break;
    }
    case ui::ET_GESTURE_TAP: {
        SetDrawBackgroundAsActive(false);
        controller_->ClickOnNotification(notification_id_);
        event->SetHandled();
        return;
    }
    default: {
        // Do nothing
    }
    }

    SlideOutView::OnGestureEvent(event);
    // Do not return here by checking handled(). SlideOutView calls SetHandled()
    // even though the scroll gesture doesn't make no (or little) effects on the
    // slide-out behavior. See http://crbug.com/172991

    if (!event->IsScrollGestureEvent() && !event->IsFlingScrollEvent())
        return;

    if (scroller_)
        scroller_->OnGestureEvent(event);
    event->SetHandled();
}

void MessageView::ButtonPressed(views::Button* sender,
    const ui::Event& event)
{
    if (sender == close_button()) {
        controller_->RemoveNotification(notification_id_, true); // By user.
    }
}

void MessageView::OnSlideOut()
{
    controller_->RemoveNotification(notification_id_, true); // By user.
}

void MessageView::SetDrawBackgroundAsActive(bool active)
{
    if (!switches::IsTouchFeedbackEnabled())
        return;
    background_view_->background()->SetNativeControlColor(active ? kHoveredButtonBackgroundColor : kNotificationBackgroundColor);
    SchedulePaint();
}

} // namespace message_center
